package com.peas.platform.framework.mybatisplus.domain;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.peas.platform.common.core.annotation.Comment;
import com.peas.platform.common.core.component.FutureTaskCall;
import com.peas.platform.common.core.constant.Constants;
import com.peas.platform.common.core.dto.BaseDto;
import com.peas.platform.common.core.exception.BaseException;
import com.peas.platform.common.core.utils.ValidatorUtil;
import com.peas.platform.framework.mybatisplus.annotation.AggregationFunc;
import com.peas.platform.framework.mybatisplus.annotation.Condition;
import com.peas.platform.framework.mybatisplus.annotation.ConditionOperator;
import com.peas.platform.framework.mybatisplus.convertor.BaseConvertor;
import com.peas.platform.framework.mybatisplus.po.BasePo;
import com.peas.platform.framework.mybatisplus.utils.PageUtil;
import lombok.SneakyThrows;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;
import java.util.function.Function;

/**
 * @param <D> Dto
 * @param <P> Po
 * @author caizhh
 */
public abstract class BaseDomainImpl<D extends BaseDto, P extends BasePo> implements BaseDomain<D, P> {

    private final Log log = LogFactory.getLog(getClass());

    @Resource
    private SqlSessionFactory sqlSessionFactory;

    private void copyBaseProperties(P source, D target) {
        Assert.notNull(source, "数据为空");
        Assert.notNull(source.getId(), "ID为空");
        target.setId(source.getId());
        target.setDeleted(source.getDeleted());
        target.setCreateBy(source.getCreateBy());
        target.setCreateTime(source.getCreateTime());
        target.setUpdateBy(source.getUpdateBy());
        target.setUpdateTime(source.getUpdateTime());
        target.setVersion(source.getVersion());
    }

    private void copyBaseProperties(List<P> sources, List<D> targets) {
        for (int i = 0; i < sources.size(); i++) {
            P s = sources.get(i);
            D t = targets.get(i);
            copyBaseProperties(s, t);
        }
    }

    private P toPo(D dto, boolean validate) {
        if (dto == null) {
            return null;
        }
        P po = BeanUtil.copyProperties(dto, this.getPoClass());
        if (validate) {
            ValidatorUtil.validateObj(po);
        }
        return po;
    }

    private List<P> toPo(List<D> list, boolean validate) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        List<P> ps = new ArrayList<>();
        for (D dto : list) {
            if (dto != null) {
                ps.add(BeanUtil.copyProperties(dto, this.getPoClass()));
            }
        }
        if (CollUtil.isEmpty(ps)) {
            return Collections.emptyList();
        }
        if (validate) {
            ValidatorUtil.validateList(ps);
        }
        return ps;
    }

    @Override
    public BaseConvertor<D, P> getConvertor() {
        return null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Class<D> getDtoClass() {
        return (Class<D>) ClassUtil.getTypeArgument(getClass(), 0);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Class<P> getPoClass() {
        return (Class<P>) ClassUtil.getTypeArgument(getClass(), 1);
    }

    @SneakyThrows
    @Override
    public QueryWrapper<P> buildWrapper(Map<String, Object> fieldValue) {
        Class<P> poClass = this.getPoClass();
        QueryWrapper<P> wrapper = Wrappers.query(poClass.newInstance());
        wrapper.eq("deleted", 0);
        if (CollUtil.isEmpty(fieldValue)) {
            return wrapper;
        }
        Field[] fields = ClassUtil.getDeclaredFields(this.getDtoClass());
        for (Field f : fields) {
            f.setAccessible(true);
            if (fieldValue.containsKey(f.getName())) {
                Object val = fieldValue.get(f.getName());
                if (val == null) {
                    continue;
                }
                if (val instanceof String) {
                    Field poField = ClassUtil.getDeclaredField(poClass, f.getName());
                    Assert.notNull(poField, "找不到String类型属性[{}]", f.getName());
                    poField.setAccessible(true);
                    TableField tf = poField.getAnnotation(TableField.class);
                    wrapper.likeRight(tf.value(), fieldValue.get(f.getName()));
                }
                if (val instanceof Number) {
                    Field poField = ClassUtil.getDeclaredField(poClass, f.getName());
                    Assert.notNull(poField, "找不到Number类型属性[{}]", f.getName());
                    poField.setAccessible(true);
                    TableField tf = poField.getAnnotation(TableField.class);
                    wrapper.eq(tf.value(), fieldValue.get(f.getName()));
                }
                if (val instanceof Date) {
//                    Field poField = ClassUtil.getDeclaredField(poClass, f.getName());
//                    Assert.notNull(poField, "找不到Date类型属性[{}]", f.getName());
//                    poField.setAccessible(true);
//                    TableField tf = poField.getAnnotation(TableField.class);
//                    wrapper.eq(tf.value(), fieldValue.get(f.getName()));
                    if (f.getName().endsWith(Constants.CRITERIA_START)) {
                        Field poField = ClassUtil.getDeclaredField(poClass, f.getName().substring(0, f.getName().length() - Constants.CRITERIA_START.length()));
                        if (poField == null) {
                            poField = ClassUtil.getDeclaredField(BasePo.class, f.getName().substring(0, f.getName().length() - Constants.CRITERIA_START.length()));
                        }
                        if (poField == null) {
                            continue;
                        }
                        poField.setAccessible(true);
                        TableField tf = poField.getAnnotation(TableField.class);
                        if (tf != null) {
                            wrapper.ge(tf.value(), fieldValue.get(f.getName()));
                        }
                    }
                    if (f.getName().endsWith(Constants.CRITERIA_END)) {
                        Field poField = ClassUtil.getDeclaredField(poClass, f.getName().substring(0, f.getName().length() - Constants.CRITERIA_END.length()));
                        if (poField == null) {
                            poField = ClassUtil.getDeclaredField(BasePo.class, f.getName().substring(0, f.getName().length() - Constants.CRITERIA_END.length()));
                        }
                        if (poField == null) {
                            continue;
                        }
                        poField.setAccessible(true);
                        TableField tf = poField.getAnnotation(TableField.class);
                        if (tf != null) {
                            wrapper.le(tf.value(), fieldValue.get(f.getName()));
                        }
                    }
                }
            }
        }
        return wrapper;
    }

    @SneakyThrows
    @Override
    public QueryWrapper<P> buildWrappers(Map<String, Collection<? extends Serializable>> fieldValues) {
        Class<P> poClass = this.getPoClass();
        QueryWrapper<P> wrapper = Wrappers.query(poClass.newInstance());
        wrapper.eq("deleted", 0);
        if (CollUtil.isEmpty(fieldValues)) {
            return wrapper;
        }
        Field[] fields = ClassUtil.getDeclaredFields(this.getDtoClass());
        for (Field f : fields) {
            f.setAccessible(true);
            if (fieldValues.containsKey(f.getName()) && CollUtil.isNotEmpty(fieldValues.get(f.getName()))) {
                Field poField = ClassUtil.getDeclaredField(poClass, f.getName());
                if (poField != null) {
                    poField.setAccessible(true);
                    TableField tf = poField.getAnnotation(TableField.class);
                    wrapper.in(tf.value(), fieldValues.get(f.getName()));
                }
            }
        }
        return wrapper;
    }

    @SneakyThrows
    @Override
    public QueryWrapper<P> buildWrapper(D condition) {
        Class<D> dtoClass = this.getDtoClass();
        Class<P> poClass = this.getPoClass();
        QueryWrapper<P> wrapper = Wrappers.query(poClass.newInstance());
        wrapper.eq("deleted", 0);
        if (condition == null) {
            return wrapper;
        }
        if (condition.getId() != null) {
            wrapper.eq("id", condition.getId());
        }
        if (condition.getVersion() != null) {
            wrapper.eq("version", condition.getVersion());
        }
        Field[] fields = ClassUtil.getDeclaredFields(dtoClass);
        for (Field f : fields) {
            f.setAccessible(true);
            Method method = ClassUtil.getPublicMethod(dtoClass, "get" + f.getName().substring(0, 1).toUpperCase() + f.getName().substring(1));
            Object o = method.invoke(condition);
            if (o == null) {
                continue;
            }
            ConditionOperator co = ConditionOperator.LIKE;
            Condition c = f.getAnnotation(Condition.class);
            if (c != null) {
                co = c.operator();
            }
            Class<?> type = f.getType();
            if (type == String.class) {
                if (!StrUtil.isEmptyIfStr(o)) {
                    Field poField = ClassUtil.getDeclaredField(poClass, f.getName());
                    if (poField == null) {
                        continue;
                    }
                    poField.setAccessible(true);
                    TableField tf = poField.getAnnotation(TableField.class);
                    if (tf != null) {
                        switch (co) {
                            case IS_NULL:
                                wrapper.isNull(tf.value());
                                break;
                            case IS_NOT_NULL:
                                wrapper.isNotNull(tf.value());
                                break;
                            case EQUAL:
                                wrapper.eq(tf.value(), o);
                                break;
                            case NOT_EQUAL:
                                wrapper.ne(tf.value(), o);
                                break;
                            case LIKE:
                                wrapper.like(tf.value(), o);
                                break;
                            case LIKE_LEFT:
                                wrapper.likeLeft(tf.value(), o);
                                break;
                            case LIKE_RIGHT:
                                wrapper.likeRight(tf.value(), o);
                                break;
                            case NOT_LIKE:
                                wrapper.notLike(tf.value(), o);
                                break;
                            case NOT_LIKE_LEFT:
                                log.error(ConditionOperator.NOT_LIKE_LEFT + " not supported");
                                throw new BaseException(ConditionOperator.NOT_LIKE_LEFT + " not supported");
                            case NOT_LIKE_RIGHT:
                                log.error(ConditionOperator.NOT_LIKE_RIGHT + " not supported");
                                throw new BaseException(ConditionOperator.NOT_LIKE_RIGHT + " not supported");
                            case LESS:
                                wrapper.lt(tf.value(), o);
                                break;
                            case LESS_EQUAL:
                                wrapper.le(tf.value(), o);
                                break;
                            case LARGE:
                                wrapper.gt(tf.value(), o);
                                break;
                            case LARGE_EQUAL:
                                wrapper.ge(tf.value(), o);
                                break;
                            case BETWEEN:
                                log.error(ConditionOperator.BETWEEN + " not supported");
                                throw new BaseException(ConditionOperator.BETWEEN + " not supported");
                            case NOT_BETWEEN:
                                log.error(ConditionOperator.NOT_BETWEEN + " not supported");
                                throw new BaseException(ConditionOperator.NOT_BETWEEN + " not supported");
                            case IN:
                                wrapper.in(tf.value(), StrUtil.split(o.toString().replaceAll(CharSequenceUtil.SPACE, CharSequenceUtil.EMPTY), StrUtil.COMMA));
                                break;
                            case NOT_IN:
                                log.error(ConditionOperator.NOT_IN + " not supported");
                                throw new BaseException(ConditionOperator.NOT_IN + " not supported");
                        }
                    }
                }
            }
            if (type == Long.class || type == Integer.class || type == BigDecimal.class) {
                Field poField = ClassUtil.getDeclaredField(poClass, f.getName());
                if (poField == null) {
                    continue;
                }
                poField.setAccessible(true);
                TableField tf = poField.getAnnotation(TableField.class);
                if (tf != null) {
                    wrapper.eq(tf.value(), o);
                }
            }
            if (type == Date.class) {
                if (f.getName().endsWith(Constants.CRITERIA_START)) {
                    Field poField = ClassUtil.getDeclaredField(poClass, f.getName().substring(0, f.getName().length() - Constants.CRITERIA_START.length()));
                    if (poField == null) {
                        poField = ClassUtil.getDeclaredField(BasePo.class, f.getName().substring(0, f.getName().length() - Constants.CRITERIA_START.length()));
                    }
                    if (poField == null) {
                        continue;
                    }
                    poField.setAccessible(true);
                    TableField tf = poField.getAnnotation(TableField.class);
                    if (tf != null) {
                        wrapper.ge(tf.value(), o);
                    }
                }
                if (f.getName().endsWith(Constants.CRITERIA_END)) {
                    Field poField = ClassUtil.getDeclaredField(poClass, f.getName().substring(0, f.getName().length() - Constants.CRITERIA_END.length()));
                    if (poField == null) {
                        poField = ClassUtil.getDeclaredField(BasePo.class, f.getName().substring(0, f.getName().length() - Constants.CRITERIA_END.length()));
                    }
                    if (poField == null) {
                        continue;
                    }
                    poField.setAccessible(true);
                    TableField tf = poField.getAnnotation(TableField.class);
                    if (tf != null) {
                        wrapper.le(tf.value(), o);
                    }
                }
            }
        }
        return wrapper;
    }

    @Override
    public List<OrderItem> buildOrderList(String sortField, String sortType) {
        Class<P> poClass = this.getPoClass();
        List<OrderItem> list = new ArrayList<>();
        Assert.notBlank(sortField, "排序字段不能为空");
        Field poField = ClassUtil.getDeclaredField(poClass, sortField);
        if (poField == null) {
            poField = ClassUtil.getDeclaredField(BasePo.class, sortField);
        }
        Assert.notNull(poField, "找不到属性[{}]", sortField);
        poField.setAccessible(true);
        TableId ti = poField.getAnnotation(TableId.class);
        boolean asc = StrUtil.equalsIgnoreCase(sortType, Constants.SORT_ASC);
        if (ti != null && StrUtil.isNotBlank(ti.value())) {
            list.add(asc ? OrderItem.asc(ti.value()) : OrderItem.desc(ti.value()));
        }
        TableField tf = poField.getAnnotation(TableField.class);
        if (tf != null && StrUtil.isNotBlank(tf.value())) {
            list.add(asc ? OrderItem.asc(tf.value()) : OrderItem.desc(tf.value()));
        }
        return list;
    }

    @Override
    public List<OrderItem> buildOrderList(Page<D> page) {
        Class<P> poClass = this.getPoClass();
        List<OrderItem> list = new ArrayList<>();
        if (page == null || CollUtil.isEmpty(page.orders())) {
            return Collections.singletonList(OrderItem.desc("id"));
        }
        for (OrderItem oi : page.orders()) {
            if (oi == null || StrUtil.isBlank(oi.getColumn())) {
                continue;
            }
            Field poField = ClassUtil.getDeclaredField(poClass, oi.getColumn());
            if (poField == null) {
                poField = ClassUtil.getDeclaredField(BasePo.class, oi.getColumn());
            }
            Assert.notNull(poField, "找不到属性[{}]", oi.getColumn());
            poField.setAccessible(true);
            TableId ti = poField.getAnnotation(TableId.class);
            if (ti != null) {
                OrderItem o = new OrderItem(ti.value(), oi.isAsc());
                list.add(o);
            }
            TableField tf = poField.getAnnotation(TableField.class);
            if (tf != null) {
                OrderItem o = new OrderItem(tf.value(), oi.isAsc());
                list.add(o);
            }
        }
        return list;
    }

    @SneakyThrows
    private Page<D> pageByFieldCollection(String field, Collection<Serializable> values, Page<D> page) {
        if (CollUtil.isEmpty(values)) {
            return Page.of(1, 0);
        }
        boolean found = false;
        Field[] fields = ClassUtil.getDeclaredFields(this.getDtoClass());
        for (Field f : fields) {
            f.setAccessible(true);
            if (StrUtil.equals(field, f.getName())) {
                found = true;
                break;
            }
        }
        Assert.isTrue(found, "找不到属性[" + field + "]");
        D d = this.getDtoClass().newInstance();
        d.setDeleted(0);
        Map<String, Collection<? extends Serializable>> map = new HashMap<>();
        map.put(field, values);
        QueryWrapper<P> wrapper = buildWrappers(map);
        Page<P> pager = PageUtil.copyPage(page, this.getPoClass());
        Page<P> ps = getMapper().selectPage(pager, wrapper);
        return PageUtil.copyPage(ps, this.getDtoClass());
    }

    @SneakyThrows
    private List<D> listByFieldCollection(String field, Collection<Serializable> values) {
        if (CollUtil.isEmpty(values)) {
            return Collections.emptyList();
        }
        if (values.size() > split_size) {
            return listByFieldCollectionAsync(field, values, split_size);
        }
        boolean found = false;
        Field[] fields = ClassUtil.getDeclaredFields(this.getDtoClass());
        for (Field f : fields) {
            f.setAccessible(true);
            if (StrUtil.equals(field, f.getName())) {
                found = true;
                break;
            }
        }
        Assert.isTrue(found, "找不到属性[" + field + "]");
        D d = this.getDtoClass().newInstance();
        d.setDeleted(0);
        Map<String, Collection<? extends Serializable>> map = new HashMap<>();
        map.put(field, values);
        QueryWrapper<P> queryWrapper = buildWrappers(map);
        queryWrapper.orderByDesc("id");
        List<P> ps = getMapper().selectList(queryWrapper);
        return BeanUtil.copyToList(ps, this.getDtoClass());
    }

    @SneakyThrows
    private List<D> listByFieldCollectionAsync(String field, Collection<Serializable> values, int splitSize) {
        if (CollUtil.isEmpty(values)) {
            return Collections.emptyList();
        }
        if (splitSize <= split_size) {
            splitSize = split_size;
        }
        StopWatch sw = new StopWatch();
        sw.start();
        List<D> result = new ArrayList<>();
        List<List<Serializable>> list = CollUtil.split(values, splitSize);
        final CountDownLatch countDownLatch = new CountDownLatch(list.size());
        List<FutureTask<List<D>>> fts = new ArrayList<>();
        list.parallelStream().forEach(items -> {
            FutureTask<List<D>> task = new FutureTask<>(() -> {
                try {
                    return listByField(field, items);
                } finally {
                    countDownLatch.countDown();
                }
            });
            fts.add(task);
            new Thread(task).start();
        });
        countDownLatch.await();
        for (FutureTask<List<D>> ft : fts) {
            if (ft.get() != null) {
                result.addAll(ft.get());
            }
        }
        sw.stop();
        return result;
    }

    @SneakyThrows
    private Boolean executeBatchAsync(List<D> dtoList, String oper, int splitSize) {
        if (CollUtil.isEmpty(dtoList)) {
            return false;
        }
        if (splitSize <= 0) {
            splitSize = split_size;
        }
        Assert.isTrue(dtoList.size() <= default_batch_size, "批量操作数量不能超过" + default_batch_size + "条。");
        List<Boolean> result = new ArrayList<>();
        List<List<D>> splits = CollUtil.split(dtoList, splitSize);
        final CountDownLatch countDownLatch = new CountDownLatch(splits.size());
        splits.parallelStream().forEach(dtos -> {
            List<Boolean> r = new FutureTaskCall<List<D>, List<Boolean>>().submit(new Function<List<D>, List<Boolean>>() {
                @Transactional(propagation = Propagation.REQUIRES_NEW)
                @Override
                public List<Boolean> apply(List<D> dtos) {
                    try {
                        StopWatch sw = new StopWatch();
                        sw.start();
                        switch (oper.toLowerCase()) {
                            case "insert":
                                BaseDomainImpl.this.saveBatch(dtos);
                                break;
                            case "update":
                                BaseDomainImpl.this.updateBatchById(dtos);
                                break;
                            case "delete":
                                BaseDomainImpl.this.removeBatchByList(dtos);
                                break;
                        }
                        sw.stop();
                        List<Boolean> r = new ArrayList<>(dtos.size());
                        for (int i = 0; i < dtos.size(); i++) {
                            r.add(true);
                        }
                        return r;
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                        return Collections.emptyList();
                    } finally {
                        countDownLatch.countDown();
                    }
                }
            }, dtos);
            result.addAll(r);
        });
        countDownLatch.await();
        return result.size() == dtoList.size();
    }

    @Transactional
    @Override
    public int save(@Valid D dto) {
        if (dto == null) {
            return 0;
        }
        dto.setId(null);
        P p = this.toPo(dto, true);
        int result = getMapper().insert(p);
        copyBaseProperties(p, dto);
        return result;
    }

    @Transactional
    @Override
    public boolean saveBatch(List<D> dtoList) {
        if (CollUtil.isEmpty(dtoList)) {
            return true;
        }
        dtoList.forEach(dto -> dto.setId(null));
        List<P> ps = this.toPo(dtoList, true);
        if (CollUtil.isEmpty(ps)) {
            return true;
        }
        String sqlStatement = SqlHelper.getSqlStatement(getRepositoryClass(), SqlMethod.INSERT_ONE);
        boolean result = SqlHelper.executeBatch(sqlSessionFactory, this.log, ps, default_batch_size, (sqlSession, po) -> sqlSession.insert(sqlStatement, po));
        copyBaseProperties(ps, dtoList);
        return result;
    }

    @Transactional
    @Override
    public boolean saveBatchAsync(List<D> dtoList) {
        return executeBatchAsync(dtoList, "insert", split_size);
    }

    @Transactional
    @Override
    public boolean saveBatchAsync(List<D> dtoList, int splitSize) {
        return executeBatchAsync(dtoList, "insert", splitSize);
    }

    @Transactional
    @Override
    public int removeById(Serializable id) {
        return getMapper().deleteById(id);
    }

    @Transactional
    @Override
    public int removeById(D dto) {
        Assert.notNull(dto.getId(), "ID不能为空");
        int result = getMapper().deleteById(dto.getId());
        Assert.isTrue(result > 0, "删除失败，请刷新数据");
        return result;
    }

    @Transactional
    @Override
    public int remove(D condition) {
        Assert.notNull(condition.getId(), "ID不能为空");
        P p = this.toPo(condition, false);
        int result = getMapper().deleteById(p);
        Assert.isTrue(result > 0, "删除失败，请刷新数据");
        return result;
    }

    @Transactional
    @Override
    public boolean removeBatchByList(List<D> dtoList) {
        if (CollUtil.isEmpty(dtoList)) {
            return true;
        }
        dtoList.forEach(dto -> Assert.notNull(dto.getId(), "[批量删除]ID不能为空"));
        List<P> ps = this.toPo(dtoList, false);
        if (CollUtil.isEmpty(ps)) {
            return true;
        }
        String sqlStatement = SqlHelper.getSqlStatement(getRepositoryClass(), SqlMethod.DELETE_BY_ID);
        return SqlHelper.executeBatch(sqlSessionFactory, this.log, ps, default_batch_size, (sqlSession, po) -> sqlSession.delete(sqlStatement, po));
    }

    @Transactional
    @Override
    public boolean removeBatchByIds(List<? extends Serializable> idList) {
        if (CollUtil.isEmpty(idList)) {
            return true;
        }
        List<D> dtoList = this.listByIds(idList);
        return removeBatchByList(dtoList);
    }

    @Transactional
    @Override
    public boolean removeBatchByListAsync(List<D> dtoList) {
        return executeBatchAsync(dtoList, "delete", split_size);
    }

    @Transactional
    @Override
    public boolean removeBatchByListAsync(List<D> dtoList, int splitSize) {
        return executeBatchAsync(dtoList, "delete", splitSize);
    }

    @Transactional
    @Override
    public int updateById(@Valid D dto) {
        Assert.notNull(dto.getId(), "ID不能为空");
        Assert.notNull(dto.getVersion(), "version不能为空");
        P p = this.toPo(dto, false);
        int result = getMapper().updateById(p);
        Assert.isTrue(result > 0, "修改失败，请刷新数据");
        copyBaseProperties(p, dto);
        return result;
    }

    @Transactional
    @Override
    public boolean updateBatchById(List<D> dtoList) {
        if (CollUtil.isEmpty(dtoList)) {
            return true;
        }
        dtoList.forEach(dto -> Assert.notNull(dto.getId(), "[批量修改]ID不能为空"));
        List<P> ps = this.toPo(dtoList, false);
        if (CollUtil.isEmpty(ps)) {
            return true;
        }
        String sqlStatement = SqlHelper.getSqlStatement(getRepositoryClass(), SqlMethod.UPDATE_BY_ID);
        boolean result = SqlHelper.executeBatch(sqlSessionFactory, this.log, ps, default_batch_size, (sqlSession, p) -> {
            MapperMethod.ParamMap<P> param = new MapperMethod.ParamMap<>();
            param.put(com.baomidou.mybatisplus.core.toolkit.Constants.ENTITY, p);
            sqlSession.update(sqlStatement, param);
        });
        copyBaseProperties(ps, dtoList);
        return result;
    }

    @Transactional
    @Override
    public boolean updateBatchByIdAsync(List<D> dtoList) {
        return executeBatchAsync(dtoList, "update", split_size);
    }

    @Transactional
    @Override
    public boolean updateBatchByIdAsync(List<D> dtoList, int splitSize) {
        return executeBatchAsync(dtoList, "update", splitSize);
    }

    @Transactional
    @Override
    public int saveOrUpdate(@Valid D dto) {
        if (dto == null) {
            return 0;
        }
        if (dto.getId() == null) {
            return save(dto);
        }
        D bean = getById(dto.getId());
        return bean == null ? save(dto) : updateById(dto);
    }

    @Override
    public D getById(Serializable id) {
        P p = getMapper().selectById(id);
        if (p == null) {
            return null;
        }
        return BeanUtil.copyProperties(p, this.getDtoClass());
    }

    @Override
    public List<D> listByIds(List<? extends Serializable> idList) {
        List<P> ps = getMapper().selectBatchIds(idList);
        if (CollUtil.isEmpty(ps)) {
            return null;
        }
        return BeanUtil.copyToList(ps, this.getDtoClass());
    }

    @Override
    public List<D> listByField(String field, Serializable value) {
        return listByField(field, Collections.singletonList(value));
    }

    @Override
    public Page<D> pageByField(String field, Serializable value, Page<D> page) {
        return this.pageByFieldCollection(field, Collections.singletonList(value), page);
    }

    @Override
    public List<D> listByField(String field, List<? extends Serializable> values) {
        Collection<Serializable> coll = new ArrayList<>(values);
        return this.listByFieldCollection(field, coll);
    }

    @Override
    public Page<D> pageByField(String field, List<? extends Serializable> values, Page<D> page) {
        Collection<Serializable> coll = new ArrayList<>(values);
        return this.pageByFieldCollection(field, coll, page);
    }

    @Override
    public List<D> listByField(String field, Set<? extends Serializable> values) {
        Collection<Serializable> coll = new HashSet<>(values);
        return this.listByFieldCollection(field, coll);
    }

    @Override
    public Page<D> pageByField(String field, Set<? extends Serializable> values, Page<D> page) {
        Collection<Serializable> coll = new ArrayList<>(values);
        return this.pageByFieldCollection(field, coll, page);
    }

    @Override
    public List<D> listByFieldAsync(String field, List<? extends Serializable> values) {
        return listByFieldAsync(field, values, split_size);
    }

    @Override
    public List<D> listByFieldAsync(String field, List<? extends Serializable> values, int splitSize) {
        Collection<Serializable> coll = new ArrayList<>(values);
        return listByFieldCollectionAsync(field, coll, splitSize);
    }

    @Override
    public List<D> listByFieldAsync(String field, Set<? extends Serializable> values) {
        return listByFieldAsync(field, values, split_size);
    }

    @Override
    public List<D> listByFieldAsync(String field, Set<? extends Serializable> values, int splitSize) {
        Collection<Serializable> coll = new HashSet<>(values);
        return listByFieldCollectionAsync(field, coll, splitSize);
    }

    @Override
    public List<D> listByMap(Map<String, Object> columnMap) {
        List<P> ps = getMapper().selectByMap(columnMap);
        if (CollUtil.isEmpty(ps)) {
            return null;
        }
        return BeanUtil.copyToList(ps, this.getDtoClass());
    }

    @Override
    public D getOne(D condition) {
        return getOne(condition, true);
    }

    @Override
    public D getOne(D condition, boolean throwEx) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        queryWrapper.orderByDesc("id");
        List<P> ps = getMapper().selectList(queryWrapper);
        if (CollUtil.isEmpty(ps)) {
            if (throwEx) {
                throw new BaseException("找不到数据");
            }
            return null;
        }
        return BeanUtil.copyProperties(CollUtil.getFirst(ps), this.getDtoClass());
    }

    @Override
    public Map<String, Object> getMap(D condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        queryWrapper.orderByDesc("id");
        List<P> ps = getMapper().selectList(queryWrapper);
        return BeanUtil.beanToMap(CollUtil.getFirst(ps));
    }

    @Override
    public long count() {
        return count(null);
    }

    @Override
    public long count(D condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        return SqlHelper.retCount(getMapper().selectCount(queryWrapper));
    }

    @SneakyThrows
    private Page<Map<String, Object>> aggregation(AggregationFunc func, String field, Page<D> page, D condition, String... groupField) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        Field f = getPoClass().getDeclaredField(field);
        f.setAccessible(true);
        TableField tf = f.getAnnotation(TableField.class);
        Assert.notNull(tf, "属性[]没有注解TableField", field);
        switch (func) {
            case SUM:
                queryWrapper.select("ifnull(sum(" + tf.value() + "),0) as result");
                break;
            case AVG:
                queryWrapper.select("ifnull(avg(" + tf.value() + "),0) as result");
                break;
            case MAX:
                queryWrapper.select("ifnull(max(" + tf.value() + "),0) as result");
                break;
            case MIN:
                queryWrapper.select("ifnull(min(" + tf.value() + "),0) as result");
                break;
            case ABS:
                queryWrapper.select("ifnull(abs(" + tf.value() + "),0) as result");
                break;
        }
        if (ArrayUtil.isNotEmpty(groupField)) {
            List<String> columns = new ArrayList<>();
            for (String group : groupField) {
                if (StrUtil.isBlank(group)) {
                    continue;
                }
                Field gf = getPoClass().getDeclaredField(group);
                gf.setAccessible(true);
                TableField tf1 = gf.getAnnotation(TableField.class);
                Assert.notNull(tf1, "属性[]没有注解TableField", gf);
                queryWrapper.select(tf1.value() + " as " + group);
                columns.add(tf1.value());
            }
            if (CollUtil.isNotEmpty(columns)) {
                queryWrapper.groupBy(columns);
            }
        }
        Page<Map<String, Object>> pager = Page.of(page.getCurrent(), page.getSize());
        return this.getMapper().selectMapsPage(pager, queryWrapper);
    }

    @SuppressWarnings("unchecked")
    private <T extends Serializable> T aggregation(AggregationFunc func, String field, D condition) {
        Page<Map<String, Object>> result = aggregation(func, field, Page.of(1, 1), condition, CharSequenceUtil.EMPTY);
        if (result == null || CollUtil.isEmpty(result.getRecords())) {
            return null;
        }
        return (T) result.getRecords().get(0).get("result");
    }

    @Override
    public <T extends Serializable> T abs(String field, D condition) {
        return aggregation(AggregationFunc.ABS, field, condition);
    }

    @Override
    public Page<Map<String, Object>> abs(String field, Page<D> page, D condition, String... groupField) {
        return aggregation(AggregationFunc.ABS, field, page, condition, groupField);
    }

    @Override
    public <T extends Serializable> T avg(String field, D condition) {
        return aggregation(AggregationFunc.AVG, field, condition);
    }

    @Override
    public Page<Map<String, Object>> avg(String field, Page<D> page, D condition, String... groupField) {
        return aggregation(AggregationFunc.AVG, field, page, condition, groupField);
    }

    @Override
    public <T extends Serializable> T sum(String field, D condition) {
        return aggregation(AggregationFunc.SUM, field, condition);
    }

    @Override
    public Page<Map<String, Object>> sum(String field, Page<D> page, D condition, String... groupField) {
        return aggregation(AggregationFunc.SUM, field, page, condition, groupField);
    }

    @Override
    public <T extends Serializable> T max(String field, D condition) {
        return aggregation(AggregationFunc.MAX, field, condition);
    }

    @Override
    public Page<Map<String, Object>> max(String field, Page<D> page, D condition, String... groupField) {
        return aggregation(AggregationFunc.MAX, field, page, condition, groupField);
    }

    @Override
    public <T extends Serializable> T min(String field, D condition) {
        return aggregation(AggregationFunc.MIN, field, condition);
    }

    @Override
    public Page<Map<String, Object>> min(String field, Page<D> page, D condition, String... groupField) {
        return aggregation(AggregationFunc.MIN, field, page, condition, groupField);
    }

    @SneakyThrows
    @Override
    public void checkRepetition(String field, String value) {
        Assert.notBlank(value, "参数错误");
        Field poField = ClassUtil.getDeclaredField(this.getPoClass(), field);
        Field dtoField = ClassUtil.getDeclaredField(this.getDtoClass(), field);
        Assert.notNull(poField, "找不到属性[{}]", field);
        Assert.notNull(dtoField, "找不到属性[{}]", field);
        poField.setAccessible(true);
        dtoField.setAccessible(true);
        String prefix = StrUtil.EMPTY;
        Comment comment = poField.getAnnotation(Comment.class);
        if (comment != null) {
            prefix = comment.value();
        }
        Assert.notContain(value, StrUtil.SPACE, prefix + "不能包含空格");
        D con = getDtoClass().newInstance();
        dtoField.set(con, value);
        long count = this.count(con);
        Assert.equals(count, 1L, prefix + "[{}]的数据已经存在", value);
    }

    @Override
    public List<D> list() {
        return list(null);
    }

    @Override
    public List<D> list(D condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        queryWrapper.orderByDesc("id");
        List<P> ps = getMapper().selectList(queryWrapper);
        if (CollUtil.isEmpty(ps)) {
            return null;
        }
        return BeanUtil.copyToList(ps, this.getDtoClass());
    }

    @SneakyThrows
    @Override
    public Page<D> page(Page<D> page) {
        return page(page, this.getDtoClass().newInstance());
    }

    @SneakyThrows
    @Override
    public Page<D> page(Page<D> page, String sortField, String sortType) {
        return page(page, sortField, sortType, this.getDtoClass().newInstance());
    }

    @Override
    public Page<D> page(Page<D> page, D condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        Page<P> pager = Page.of(page.getCurrent(), page.getSize());
        pager.setOrders(buildOrderList(page));
        Page<P> data = getMapper().selectPage(pager, queryWrapper);
        return PageUtil.copyPage(data, this.getDtoClass());
    }

    @Override
    public Page<D> page(Page<D> page, String sortField, String sortType, D condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        Page<P> pager = Page.of(page.getCurrent(), page.getSize());
        pager.setOrders(buildOrderList(sortField, sortType));
        Page<P> data = getMapper().selectPage(pager, queryWrapper);
        return PageUtil.copyPage(data, this.getDtoClass());
    }

    @Deprecated
    @Override
    public Page<D> page(D condition) {
        Page<D> page = Page.of(Constants.DEFAULT_PAGE_NUM, Constants.DEFAULT_PAGE_SIZE);
        page.setOrders(buildOrderList(null));
        return page(page, condition);
    }

    @Override
    public Page<D> page(Page<D> page, Map<String, Object> condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        Page<P> pager = Page.of(page.getCurrent(), page.getSize());
        pager.setOrders(buildOrderList(page));
        Page<P> data = getMapper().selectPage(pager, queryWrapper);
        return PageUtil.copyPage(data, this.getDtoClass());
    }

    @Override
    public Page<D> page(Page<D> page, String sortField, String sortType, Map<String, Object> condition) {
        page.setOrders(buildOrderList(sortField, sortType));
        return page(page, condition);
    }

    @Deprecated
    @Override
    public Page<D> page(Map<String, Object> condition) {
        Page<D> page = Page.of(Constants.DEFAULT_PAGE_NUM, Constants.DEFAULT_PAGE_SIZE);
        page.setOrders(buildOrderList(null));
        return page(page, condition);
    }

    @Override
    public List<Map<String, Object>> listMaps() {
        return listMaps(null);
    }

    @Override
    public List<Map<String, Object>> listMaps(D condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        queryWrapper.orderByDesc("id");
        return getMapper().selectMaps(queryWrapper);
    }

    @SneakyThrows
    @Override
    public Page<Map<String, Object>> pageMaps(Page<D> page) {
        return pageMaps(page, this.getDtoClass().newInstance());
    }

    @SneakyThrows
    @Override
    public Page<Map<String, Object>> pageMaps(Page<D> page, String sortField, String sortType) {
        return pageMaps(page, sortField, sortType, this.getDtoClass().newInstance());
    }

    @Override
    public Page<Map<String, Object>> pageMaps(Page<D> page, D condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        Page<Map<String, Object>> pager = Page.of(page.getCurrent(), page.getSize());
        pager.setOrders(buildOrderList(page));
        return getMapper().selectMapsPage(pager, queryWrapper);
    }

    @Override
    public Page<Map<String, Object>> pageMaps(Page<D> page, String sortField, String sortType, D condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        Page<Map<String, Object>> pager = Page.of(page.getCurrent(), page.getSize());
        pager.setOrders(buildOrderList(sortField, sortType));
        return getMapper().selectMapsPage(pager, queryWrapper);
    }

    @Override
    public Page<Map<String, Object>> pageMaps(Page<D> page, Map<String, Object> condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        Page<Map<String, Object>> pager = Page.of(page.getCurrent(), page.getSize());
        pager.setOrders(buildOrderList(page));
        return getMapper().selectMapsPage(pager, queryWrapper);
    }

    @Override
    public Page<Map<String, Object>> pageMaps(Page<D> page, String sortField, String sortType, Map<String, Object> condition) {
        QueryWrapper<P> queryWrapper = buildWrapper(condition);
        Page<Map<String, Object>> pager = Page.of(page.getCurrent(), page.getSize());
        pager.setOrders(buildOrderList(sortField, sortType));
        return getMapper().selectMapsPage(pager, queryWrapper);
    }

}
